@package-verse/esmpack 1.0.10 → 1.0.12

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.
package/README.md CHANGED
@@ -2,22 +2,19 @@
2
2
  ESM Pack Packer and Web Server with PostCSS and ESM Loader
3
3
 
4
4
  # Why?
5
- Because, there is no simple packer that just rewrites module paths. After ES6 and HTTP2, there is no need to bundle all JavaScripts into a single file. Parsing and loading entire bundle is a single threaded operation, which blocks UI rendering and also consumes very high memory.
5
+ Because, there is no simple packer that just generates module paths. After ES6 and HTTP2, there is no need to bundle all JavaScripts into a single file. Parsing and loading entire bundle is a single threaded operation, which blocks UI rendering and also consumes very high memory.
6
6
 
7
7
  # So what does this do?
8
8
  1. Creates a single pack JS file which only has import definition of all the imports.
9
- 2. Packed file strips css and delivers separate css as combined CSS.
10
- 3. Retains ESM source code as it is except import path, import path is rewritten to fully qualified CDN url. So caching is preserved over different main module versions but same dependencies.
9
+ 2. Import map contains inline JavaScript module via data url to inject CSS link into document.
11
10
 
12
11
 
13
12
  ## Dev Packer
14
13
 
15
- 1. Development time packer will generate HTML file along with the pack that will generate import maps along with the loading of control and hosting it.
14
+ 1. Development time packer will generate HTML file along with the import map and inline script to host the module.
16
15
  2. Dev Packer will generate `let cs = document.currentScript;import("imported-path").then((r) => ESMPack.render(r, cs))` script inside html for every JS's corresponding html.
17
16
  3. Library author must implement `ESMPack.render` method which will accept exports from imported method and `currentScript`.
18
- 4. Every `js` file's imports will be changed to fully qualified references for external imports.
19
17
 
20
18
  ## Release Packer
21
19
 
22
- 1. A single script with `.pack.js` will be generated that will import every nested imports along with fully qualified path for every nested imported external references.
23
- 2. Every `js` file's imports will be changed to fully qualified references for external imports.
20
+ 1. `pack.js` will generate a single JS file that will inject import map into document and it will call `ESMPack.render` method with import.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@package-verse/esmpack",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "ESM Pack Packer and Web Server with PostCSS and ESM Loader",
5
5
  "homepage": "https://github.com/package-verse/esmpack#readme",
6
6
  "bugs": {
@@ -1,3 +1,7 @@
1
+ import path from "path";
2
+ import { Babel } from "../parser/babel.js";
3
+ import packageMap, { IPackageMap } from "./packageMap.js";
4
+
1
5
  /**
2
6
  * File Packer must do following tasks...
3
7
  * 1. Visit every JavaScript file
@@ -17,15 +21,18 @@
17
21
  * Imports all nested dependencies of App.js that should not contain fully qualified path
18
22
  * Import dynamically loaded modules as well
19
23
  * Imports App.js dynamically so CSS can be ready before hosting the User interface
20
- * 2. App.pack.global.css
21
- * 3. App.pack.local.css
22
- * 4. App.pack.{hash-of-absolute-module-path}.js <-- this will be a dependency for non js module such as image or json etc.
23
- * This will load an absolute path via resolve
24
24
  */
25
25
  export default class FilePacker {
26
26
 
27
- globalCss = [];
28
- localCss = [];
27
+ readonly absoluteSrc: string;
28
+
29
+ modules: IPackageMap;
30
+
31
+ readonly cssImports = [];
32
+
33
+ readonly jsonImports = [];
34
+
35
+ readonly pathImports = [];
29
36
 
30
37
  constructor(
31
38
  public readonly root: string,
@@ -33,10 +40,53 @@ export default class FilePacker {
33
40
  public readonly prefix: string
34
41
  ) {
35
42
  // empty
43
+ // let us make src relative to the root if an absolute path was supplied
44
+ if (path.isAbsolute(this.src)) {
45
+ this.src = path.relative(this.root, this.src);
46
+ }
47
+ this.absoluteSrc = path.resolve(this.root, this.src);
36
48
  }
37
49
 
38
50
  async pack() {
39
51
 
52
+ // resolve package.json
53
+ this.modules = await packageMap(this.root);
54
+
55
+ const resolve = (url, sourceFile) => this.resolve(url, sourceFile);
56
+
57
+ // we don't need the code
58
+ await Babel.transformAsync({
59
+ file: path.join(this.root, this.src),
60
+ resolve,
61
+ dynamicResolve: resolve
62
+ });
63
+
64
+
40
65
  }
41
66
 
67
+ resolve(url: string, sourceFile: string) {
68
+
69
+ const moduleUrl = this.moduleUrl(url, sourceFile);
70
+
71
+ if (url.endsWith(".css")) {
72
+ this.cssImports.push(moduleUrl);
73
+ return url;
74
+ }
75
+ if (url.endsWith(".json")) {
76
+ this.jsonImports.push(moduleUrl);
77
+ return url;
78
+ }
79
+
80
+ if (!url.endsWith(".js")) {
81
+
82
+ }
83
+ return url;
84
+ }
85
+
86
+ moduleUrl(url: string, sourceFile: string) {
87
+
88
+
89
+
90
+ return url;
91
+ }
42
92
  }
@@ -0,0 +1,27 @@
1
+ import { IPackageInfo, IPackageMap } from "./packageMap.js";
2
+
3
+ /**
4
+ * This class holds a path of single module along
5
+ * with it's own package info
6
+ */
7
+ export default class JSModule {
8
+
9
+ public imports: IPackageMap;
10
+
11
+ public owner: IPackageInfo;
12
+ public main: IPackageInfo;
13
+
14
+ public mainFolder: string;
15
+ public ownerFolder: string;
16
+
17
+ public src: string;
18
+
19
+ constructor(
20
+ p: Partial<JSModule>
21
+ ) {
22
+ // do nothing
23
+ Object.setPrototypeOf(p, JSModule.prototype);
24
+ return p as any;
25
+ }
26
+
27
+ }
@@ -0,0 +1,64 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+
4
+ const resolvedImports = new Map<string, Promise<IPackageMap>>();
5
+
6
+ export interface IPackageInfo {
7
+ name: string;
8
+ version: string;
9
+ main: string;
10
+ }
11
+
12
+ export interface IPackageMap {
13
+ [key: string]: IPackageInfo
14
+ }
15
+
16
+ async function populatePackageInfo(root, imports: IPackageMap, packageJsonPath) {
17
+ const {
18
+ name,
19
+ version,
20
+ dependencies,
21
+ type,
22
+ module: moduleMain,
23
+ main
24
+ } = JSON.parse(await readFile(packageJsonPath, "utf-8"));
25
+
26
+ if (imports[name]) {
27
+ return;
28
+ }
29
+
30
+ /**
31
+ * Dependency must specify module or main, we will not assume it.
32
+ * We want to keep import map small.
33
+ *
34
+ * Otherwise, caller must explicitly specify the fully qualified module path to load
35
+ */
36
+ imports[name] = {
37
+ name,
38
+ version,
39
+ main: moduleMain
40
+ || (type === "module" ? main : void 0)
41
+ || void 0
42
+ }
43
+
44
+ for(const key of Object.keys(dependencies)) {
45
+ if (imports[key]) {
46
+ continue;
47
+ }
48
+ await populatePackageInfo(root, imports, join(root, "node_modules", key, "package.json"));
49
+ }
50
+ }
51
+
52
+ export default function packageMap(root: string) {
53
+ let value = resolvedImports.get(root);
54
+ if(value) {
55
+ return value;
56
+ }
57
+ value = (async ()=> {
58
+ const moduleImports = {} as IPackageMap;
59
+ await populatePackageInfo(root, moduleImports, join(root, "package.json"));
60
+ return moduleImports;
61
+ })();
62
+ resolvedImports.set(root, value);
63
+ return value;
64
+ }
@@ -3,6 +3,7 @@ import { readFileSync } from "fs";
3
3
  import { ProcessOptions } from "../ProcessArgs.js";
4
4
  import { parse } from "path";
5
5
  import { CallExpression, ImportDeclaration, ImportExpression } from "@babel/types";
6
+ import { readFile } from "fs/promises";
6
7
 
7
8
 
8
9
  export class Babel {
@@ -17,14 +18,37 @@ export class Babel {
17
18
  dynamicResolve?: (url: string, sourceFile: string) => string
18
19
  }) {
19
20
 
21
+ const presets: TransformOptions = Babel.prepareOptions(file, dynamicResolve, resolve);
22
+
23
+ const p = { ... presets, filename: file };
24
+ const code = readFileSync(file, "utf8");
25
+ const result = transformSync(code, p);
26
+ return result.code;
27
+ }
28
+
29
+ static async transformAsync({
30
+ file,
31
+ resolve,
32
+ dynamicResolve
33
+ }) {
34
+ const presets: TransformOptions = Babel.prepareOptions(file, dynamicResolve, resolve);
35
+
36
+ const p = { ... presets, filename: file };
37
+ const code = await readFile(file, "utf8");
38
+ const result = transformSync(code, p);
39
+ return result.code;
40
+
41
+ }
42
+
43
+ private static prepareOptions(file: string, dynamicResolve: (url: string, sourceFile: string) => string, resolve: (url: string, sourceFile: string) => string) {
20
44
  const { base: name } = parse(file);
21
45
 
22
- function CallExpression (node: NodePath<CallExpression>) {
46
+ function CallExpression(node: NodePath<CallExpression>) {
23
47
  if (node.node.callee.type !== "Import") {
24
48
  return node;
25
49
  }
26
50
  const sourceFile = (node.hub as any)?.file?.inputMap?.sourcemap?.sources?.[0];
27
- const [ arg1 ] = node.node.arguments;
51
+ const [arg1] = node.node.arguments;
28
52
  if (!arg1) {
29
53
  return node;
30
54
  }
@@ -65,7 +89,7 @@ export class Babel {
65
89
  getModuleId: () => "v",
66
90
  "plugins": [
67
91
  [
68
- function(babel) {
92
+ function (babel) {
69
93
  return {
70
94
  name: "Import Transformer",
71
95
  visitor: {
@@ -80,19 +104,13 @@ export class Babel {
80
104
  e.source.value = source;
81
105
  return node;
82
106
  },
83
- ... (dynamicResolve ? { CallExpression, ImportExpression } : {}),
107
+ ...(dynamicResolve ? { CallExpression, ImportExpression } : {}),
84
108
  }
85
109
  };
86
110
  }
87
111
  ]
88
-
89
112
  ]
90
113
  };
91
-
92
- const p = { ... presets, filename: file };
93
- const code = readFileSync(file, "utf8");
94
- const result = transformSync(code, p);
95
- return result.code;
114
+ return presets;
96
115
  }
97
-
98
116
  }
@@ -1,6 +1,6 @@
1
- import { readFileSync } from "fs";
1
+ import { existsSync, readFileSync } from "fs";
2
2
  import { ProcessOptions } from "../../ProcessArgs.js";
3
- import { join } from "path";
3
+ import path, { join } from "path/posix";
4
4
 
5
5
  const jsonText = readFileSync(join(ProcessOptions.cwd, "package.json"), "utf-8");
6
6
 
@@ -11,7 +11,22 @@ const imports = {
11
11
  };
12
12
 
13
13
  for (const [key] of Object.entries(packageInfo.dependencies)) {
14
- imports[key + "/"] = "/node_modules/" + key + "/";
14
+
15
+ const modulePackageJsonFilePath = join(ProcessOptions.cwd, "node_modules", key , "package.json");
16
+ if (!existsSync(modulePackageJsonFilePath)) {
17
+ continue;
18
+ }
19
+
20
+ const modulePath = "/node_modules/" + key + "/";
21
+
22
+ // read package.json
23
+ const modulePackageJson = JSON.parse(readFileSync(modulePackageJsonFilePath, "utf-8"));
24
+ imports[key] = path.join(modulePath , modulePackageJson.module
25
+ || (modulePackageJson.type === "module"
26
+ ? modulePackageJson.main : "index.js"));
27
+
28
+ imports[key + "/"] = modulePath;
29
+
15
30
  }
16
31
 
17
32
  export const importMap = { imports };
@@ -1,62 +1,38 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { Babel } from "../../parser/babel.js";
3
3
  import path, { parse } from "node:path";
4
- import { ProcessOptions } from "../../ProcessArgs.js";
5
- import { existsSync, readFileSync } from "node:fs";
4
+ import { existsSync } from "node:fs";
6
5
 
7
6
  export default function sendJS(filePath: string, req: IncomingMessage, res: ServerResponse) {
8
7
 
9
8
  const resolve = (url: string, sourceFile: string) => {
10
9
 
10
+ if (url.endsWith(".js")) {
11
+ return url;
12
+ }
13
+
11
14
  if(!sourceFile) {
12
15
  sourceFile = filePath;
13
16
  }
14
17
 
15
- const originalUrl = url;
16
-
17
- // check if it has no extension...
18
- const { ext } = parse(url);
19
- if (!ext) {
18
+ if (!url.startsWith(".")) {
20
19
 
21
- // this is case for tslib, reflect_metadata
22
-
23
- // fetch module...
20
+ // we need to include .js for every module relative path
24
21
  const tokens = url.split("/");
25
22
  let packageName = tokens.shift();
26
23
  if (packageName.startsWith("@")) {
27
24
  packageName += "/" + tokens.shift();
28
25
  }
29
26
 
30
- const packageJsonPath = path.resolve(ProcessOptions.cwd, "node_modules", packageName, "package.json");
31
- if (existsSync(packageJsonPath)) {
32
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
33
- const start = packageJson["module"] || packageJson["main"];
34
- return url + "/" + start;
27
+ if (tokens.length) {
28
+ if (!url.endsWith(".js")) {
29
+ return url + ".js";
30
+ }
35
31
  }
36
32
 
37
- }
38
-
39
-
40
- if (!url.startsWith(".")) {
41
-
42
- if (!url.endsWith(".js")) {
43
- url += ".js";
44
- }
45
-
46
- return url;
47
- }
48
-
49
- const jsFile = path.resolve(path.dirname(filePath), url);
50
- if (existsSync(jsFile)) {
51
- if (!jsFile.endsWith(".js")) {
52
- url += ".js";
53
- }
54
33
  return url;
55
34
  }
56
35
 
57
- if (url.endsWith(".js")) {
58
- return url;
59
- }
60
36
 
61
37
  // is it referenced from source...
62
38
  const dir = path.dirname(filePath);
@@ -69,8 +45,9 @@ export default function sendJS(filePath: string, req: IncomingMessage, res: Serv
69
45
  }
70
46
  return relative;
71
47
  }
72
- return originalUrl;
73
48
 
49
+
50
+ return url + ".js";
74
51
  };
75
52
 
76
53
 
@@ -5,7 +5,8 @@ import sendList from "./sendList.js";
5
5
  import sendJS from "./sendJS.js";
6
6
  export default function sendLocalFile(reqPath: string, path: string, req: IncomingMessage, res: ServerResponse) {
7
7
 
8
- if (path.endsWith(".js")) {
8
+ if (path.endsWith(".js")
9
+ && !path.endsWith(".pack.js")) {
9
10
  // send JS
10
11
  return sendJS(path, req, res);
11
12
  }
@@ -1,3 +1,15 @@
1
1
  date-view {
2
2
  color: red;
3
+ display: flex;
4
+ flex-direction: row;
5
+ align-items: center;
6
+ justify-items: center;
7
+ align-content: center;
8
+ justify-content: center;
9
+ gap: 10px;
10
+ }
11
+
12
+ date-view img {
13
+ max-height: 50px;
14
+ max-width: 50px;
3
15
  }