@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 +4 -7
- package/package.json +1 -1
- package/src/pack/FilePacker.ts +56 -6
- package/src/pack/JSModule.ts +27 -0
- package/src/pack/packageMap.ts +64 -0
- package/src/parser/babel.ts +29 -11
- package/src/serve/send/packageInfo.ts +18 -3
- package/src/serve/send/sendJS.ts +13 -36
- package/src/serve/send/sendLocalFile.ts +2 -1
- package/src/test/TestView.local.css +12 -0
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
|
|
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.
|
|
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
|
|
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.
|
|
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
package/src/pack/FilePacker.ts
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
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
|
+
}
|
package/src/parser/babel.ts
CHANGED
|
@@ -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
|
|
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 [
|
|
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
|
-
...
|
|
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
|
-
|
|
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 };
|
package/src/serve/send/sendJS.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
// check if it has no extension...
|
|
18
|
-
const { ext } = parse(url);
|
|
19
|
-
if (!ext) {
|
|
18
|
+
if (!url.startsWith(".")) {
|
|
20
19
|
|
|
21
|
-
//
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
}
|