@divriots/jampack 0.0.9 → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright 2022 ‹div›RIOTS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ ## jampack
2
+
3
+ Optimize static websites for maximum performance and best Core Web Vitals scores.
@@ -0,0 +1,5 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import sharp from 'sharp';
3
+ import type { Result } from './types.js';
4
+ export declare const compressImage: (data: Buffer, resize: sharp.ResizeOptions) => Promise<Buffer | undefined>;
5
+ export declare function compress(dir: string, initResult: Result[]): Promise<void>;
@@ -0,0 +1,182 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { optimize as svgo } from "svgo";
4
+ import { minify as htmlminifier } from 'html-minifier-terser';
5
+ import { minify as csso } from "csso";
6
+ import { formatBytes } from './utils.js';
7
+ import { table } from 'table';
8
+ import sharp from 'sharp';
9
+ import swc from '@swc/core';
10
+ let state;
11
+ let compressedFiles = [];
12
+ const beginProgress = (initResults) => {
13
+ state = {
14
+ total: {
15
+ nbFiles: 0,
16
+ nbFilesCompressed: 0,
17
+ dataLenUncompressed: 0,
18
+ dataLenCompressed: 0
19
+ },
20
+ detailsByExtension: {},
21
+ };
22
+ initResults.forEach(r => {
23
+ addProgress(r);
24
+ compressedFiles.push(r.file);
25
+ });
26
+ };
27
+ const addProgress = (r) => {
28
+ const isCompressed = r.compressedSize < r.originalSize ? 1 : 0;
29
+ state.total.nbFiles++;
30
+ state.total.nbFilesCompressed += isCompressed;
31
+ state.total.dataLenUncompressed += r.originalSize;
32
+ state.total.dataLenCompressed += r.compressedSize;
33
+ const ext = path.extname(r.file);
34
+ if (ext) {
35
+ var summary = state.detailsByExtension[ext];
36
+ if (!summary) {
37
+ summary = {
38
+ nbFiles: 0,
39
+ nbFilesCompressed: 0,
40
+ dataLenUncompressed: 0,
41
+ dataLenCompressed: 0
42
+ };
43
+ state.detailsByExtension[ext] = summary;
44
+ }
45
+ summary.nbFiles++;
46
+ summary.nbFilesCompressed += isCompressed;
47
+ summary.dataLenUncompressed += r.originalSize;
48
+ summary.dataLenCompressed += r.compressedSize;
49
+ }
50
+ };
51
+ const printProgress = (r) => {
52
+ addProgress(r);
53
+ process.stdout.clearLine(0);
54
+ process.stdout.cursorTo(0);
55
+ process.stdout.write(`${state.total.nbFiles} files | ${formatBytes(state.total.dataLenUncompressed)} → ${formatBytes(state.total.dataLenCompressed)}`);
56
+ };
57
+ const endProgress = () => {
58
+ process.stdout.write('\n');
59
+ };
60
+ const printDetails = () => {
61
+ const dataTable = [['Extension', 'Files', 'Compressed', 'Original', 'Compressed', 'Gain']];
62
+ const config = {
63
+ columns: [
64
+ { alignment: 'left' },
65
+ { alignment: 'right' },
66
+ { alignment: 'right' },
67
+ { alignment: 'right' },
68
+ { alignment: 'right' }
69
+ ],
70
+ };
71
+ Object.entries(state.detailsByExtension).forEach(([ext, summary]) => {
72
+ if (summary.dataLenCompressed < summary.dataLenUncompressed) {
73
+ const row = [ext, summary.nbFiles, summary.nbFilesCompressed, formatBytes(summary.dataLenUncompressed), formatBytes(summary.dataLenCompressed), '-' + formatBytes(summary.dataLenUncompressed - summary.dataLenCompressed)];
74
+ dataTable.push(row);
75
+ }
76
+ });
77
+ const total = ['Total', state.total.nbFiles, state.total.nbFilesCompressed, formatBytes(state.total.dataLenUncompressed), formatBytes(state.total.dataLenCompressed), '-' + formatBytes(state.total.dataLenUncompressed - state.total.dataLenCompressed)];
78
+ dataTable.push(total);
79
+ console.log(table(dataTable, config));
80
+ };
81
+ const processFile = async (file, stats) => {
82
+ const result = {
83
+ file,
84
+ originalSize: stats.size,
85
+ compressedSize: stats.size
86
+ };
87
+ let writeData = undefined;
88
+ try {
89
+ const ext = path.extname(file);
90
+ switch (ext) {
91
+ case '.svg':
92
+ case '.png':
93
+ case '.jpg':
94
+ case '.jpeg':
95
+ case '.webp':
96
+ case '.gif':
97
+ const newImageData = await compressImageFile(file);
98
+ if (newImageData && newImageData.length < result.originalSize) {
99
+ writeData = newImageData;
100
+ }
101
+ break;
102
+ case '.html':
103
+ case '.htm':
104
+ const htmldata = await fs.readFile(file);
105
+ const newhtmlData = await htmlminifier(htmldata.toString(), { minifyCSS: true, minifyJS: true, sortClassName: true, sortAttributes: true });
106
+ writeData = newhtmlData;
107
+ break;
108
+ case '.css':
109
+ const cssdata = await fs.readFile(file);
110
+ const newcssData = await csso(cssdata.toString()).css;
111
+ if (newcssData) {
112
+ writeData = newcssData;
113
+ }
114
+ break;
115
+ case '.js':
116
+ const jsdata = await fs.readFile(file);
117
+ const newjsresult = await swc.minify(jsdata.toString(), { compress: true, mangle: true });
118
+ if (newjsresult.code && newjsresult.code.length < jsdata.length) {
119
+ writeData = newjsresult.code;
120
+ }
121
+ break;
122
+ }
123
+ }
124
+ catch (e) {
125
+ // console error for the moment
126
+ console.error(e);
127
+ }
128
+ // Writedata
129
+ if (writeData && writeData.length < result.originalSize) {
130
+ result.compressedSize = writeData.length;
131
+ await fs.writeFile(file, writeData);
132
+ }
133
+ printProgress(result);
134
+ return result;
135
+ };
136
+ export const compressImage = async (data, resize) => {
137
+ const sharpFile = await sharp(data);
138
+ const meta = await sharpFile.metadata();
139
+ switch (meta.format) {
140
+ case 'svg':
141
+ const newData = svgo(data, {});
142
+ if (newData.error || newData.modernError) {
143
+ console.log(`Error processing svg ${data}`);
144
+ return undefined;
145
+ }
146
+ return Buffer.from(newData.data, 'utf8');
147
+ case 'png':
148
+ case 'jpg':
149
+ case 'jpeg':
150
+ case 'webp':
151
+ case 'gif':
152
+ return await sharpFile.resize(resize).toBuffer();
153
+ }
154
+ return undefined;
155
+ };
156
+ const compressImageFile = async (file) => {
157
+ const buffer = await fs.readFile(file);
158
+ return compressImage(buffer, {});
159
+ };
160
+ const processDirectory = async (directoryPath) => {
161
+ const filesInDirectory = await fs.readdir(directoryPath);
162
+ const files = await Promise.all(filesInDirectory.map(async (file) => {
163
+ const filePath = path.join(directoryPath, file);
164
+ const stats = await fs.stat(filePath);
165
+ if (stats.isDirectory()) {
166
+ return processDirectory(filePath);
167
+ }
168
+ else {
169
+ if (compressedFiles.includes(filePath)) {
170
+ return [];
171
+ }
172
+ return [await processFile(filePath, stats)];
173
+ }
174
+ }));
175
+ return files.filter((file) => file.length).flat(1); // Remove empty dir and flat it all
176
+ };
177
+ export async function compress(dir, initResult) {
178
+ beginProgress(initResult);
179
+ await processDirectory(dir);
180
+ endProgress();
181
+ printDetails();
182
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { compress } from './compress.js';
4
+ import { optimize } from './optimize.js';
5
+ const program = new Command();
6
+ program
7
+ .name('jampack')
8
+ .description('Static website Optimizer')
9
+ .version('0.0.1');
10
+ program.command('pack', { isDefault: true })
11
+ .description('todo')
12
+ .argument('<dir>', 'Directory to pack')
13
+ .action(async (str, options) => {
14
+ let compressedResults = [];
15
+ console.log(`PASS 1 - Optimizing...`);
16
+ compressedResults = await optimize(str);
17
+ console.log(`PASS 2 - Compressing...`);
18
+ await compress(str, compressedResults);
19
+ });
20
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import type { Result } from './types.js';
2
+ export declare function optimize(dir: string): Promise<Result[]>;
@@ -0,0 +1,225 @@
1
+ import { globby } from 'globby';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs/promises';
4
+ import cheerio from 'cheerio';
5
+ import { isLocal, isNumeric, translateSrc } from './utils.js';
6
+ import sharp from "sharp";
7
+ import options from './options.js';
8
+ import { compressImage } from './compress.js';
9
+ import svgToMiniDataURI from 'mini-svg-data-uri';
10
+ const compressedImages = [];
11
+ const compressedResults = [];
12
+ async function analyse(dir, file) {
13
+ console.log('Processing ' + file);
14
+ const html = (await fs.readFile(path.join(dir, file))).toString();
15
+ const $ = cheerio.load(html, {});
16
+ const imgs = $('img');
17
+ const imgsArray = [];
18
+ imgs.each(async (index, imgElement) => {
19
+ imgsArray.push(imgElement);
20
+ });
21
+ // Process images sequentially
22
+ await imgsArray.reduce(async (previousPromise, imgElement) => {
23
+ await previousPromise;
24
+ return processImage(dir, file, $, imgElement);
25
+ }, Promise.resolve());
26
+ await fs.writeFile(path.join(dir, file), $.html());
27
+ }
28
+ async function processImage(dir, file, $, imgElement) {
29
+ const img = $(imgElement);
30
+ /*
31
+ * Attribute 'alt'
32
+ */
33
+ const attrib_alt = img.attr('alt');
34
+ if (attrib_alt === undefined) {
35
+ console.warn('<img> has no alt attribute - adding an empty but you should fix it');
36
+ img.attr('alt', "");
37
+ }
38
+ /*
39
+ * Attribute 'src'
40
+ */
41
+ const attrib_src = img.attr('src');
42
+ console.log(`- Image ${attrib_src}`);
43
+ if (!attrib_src) {
44
+ console.warn('<img> has no src attribute');
45
+ return;
46
+ }
47
+ if (attrib_src.startsWith('data:')) {
48
+ // Data URI image
49
+ // TODO: try to compress it
50
+ return;
51
+ }
52
+ if (!isLocal(attrib_src)) {
53
+ // Image not local, don't touch it
54
+ return;
55
+ }
56
+ /*
57
+ * Attribute 'loading'
58
+ */
59
+ const attr_loading = img.attr('loading');
60
+ switch (attr_loading) {
61
+ case undefined:
62
+ // Go lazy by default
63
+ img.attr('loading', 'lazy');
64
+ break;
65
+ case 'eager':
66
+ img.removeAttr('loading');
67
+ break;
68
+ case 'lazy':
69
+ // Don't touch it
70
+ break;
71
+ default:
72
+ console.warn(`Invalid loading attribute ${attr_loading}`);
73
+ }
74
+ /*
75
+ * Attribute 'decoding'
76
+ */
77
+ img.attr('decoding', 'async');
78
+ /*
79
+ * Loading image
80
+ */
81
+ const absoluteImgPath = translateSrc(dir, path.dirname(file), attrib_src);
82
+ // file exists?
83
+ try {
84
+ const stats = await fs.stat(absoluteImgPath);
85
+ }
86
+ catch (e) {
87
+ console.error(`Missing img ${absoluteImgPath}`);
88
+ return;
89
+ }
90
+ const sharpFile = sharp(absoluteImgPath);
91
+ const meta = await sharpFile.metadata();
92
+ const originalData = await fs.readFile(absoluteImgPath);
93
+ /*
94
+ * Embed small images
95
+ *
96
+ * Note: This check is done pre-compression so more opportunities could
97
+ * be done if done post compression.
98
+ */
99
+ let isEmbed = false;
100
+ if (originalData.length <= options.image.embed_size) {
101
+ let datauri = undefined;
102
+ switch (meta.format) {
103
+ case 'svg':
104
+ const compressedData = await compressImage(originalData, {});
105
+ const svgEmbed = (compressedData && compressedData.length < originalData.length) ? compressedData : originalData;
106
+ datauri = svgToMiniDataURI(svgEmbed.toString());
107
+ break;
108
+ case 'jpg':
109
+ case 'jpeg':
110
+ case 'png':
111
+ case 'webp':
112
+ case 'gif':
113
+ // TODO
114
+ break;
115
+ }
116
+ if (datauri) {
117
+ img.attr('src', datauri);
118
+ isEmbed = true;
119
+ img.removeAttr('loading');
120
+ img.removeAttr('decoding');
121
+ }
122
+ }
123
+ if (isEmbed) {
124
+ // Image is embed, no need for more processing
125
+ return;
126
+ }
127
+ /*
128
+ * Attribute 'width' & 'height'
129
+ */
130
+ const [w, h] = setImageSize(img, meta);
131
+ if (w < 0 || h < 0) {
132
+ console.warn(`Unexpected error in image size calculation - some optimizations can't be done`);
133
+ return;
134
+ }
135
+ /*
136
+ * Compress image
137
+ */
138
+ if (compressedImages.includes(absoluteImgPath)) {
139
+ // Image already compressed. Skip.
140
+ return;
141
+ }
142
+ const fileWidth = meta.width;
143
+ const fileHeight = meta.height;
144
+ const newImage = await compressImage(originalData, { width: w, height: h });
145
+ if (newImage && newImage.length < originalData.length) {
146
+ fs.writeFile(absoluteImgPath, newImage);
147
+ compressedImages.push(absoluteImgPath);
148
+ compressedResults.push({ file: absoluteImgPath, originalSize: originalData.length, compressedSize: newImage.length });
149
+ }
150
+ const attr_srcset = img.attr('srcset');
151
+ if (attr_srcset) {
152
+ // If srcset is set, don't touch it
153
+ }
154
+ else {
155
+ // Generate image set
156
+ }
157
+ }
158
+ function setImageSize(img, meta) {
159
+ let width = img.attr('width');
160
+ let height = img.attr('height');
161
+ let width_new = undefined;
162
+ let height_new = undefined;
163
+ // Check valid values
164
+ if (width !== undefined) {
165
+ if (!isNumeric(width)) {
166
+ console.warn(`Invalid width attribute "${width}" - overriding`);
167
+ width = undefined;
168
+ }
169
+ }
170
+ if (height !== undefined) {
171
+ if (!isNumeric(height)) {
172
+ console.warn(`Invalid width attribute "${height}" - overriding`);
173
+ height = undefined;
174
+ }
175
+ }
176
+ // If we don't have the metadata, we can't do much more
177
+ if (meta.width === undefined || meta.height === undefined) {
178
+ return [-1, -1];
179
+ }
180
+ const originalRatio = meta.width / meta.height;
181
+ if (width !== undefined && height !== undefined) {
182
+ // Both are provided
183
+ const w = parseInt(width, 10);
184
+ const h = parseInt(height, 10);
185
+ // Is ratio equal?
186
+ const providedRatio = Math.round(w / h * 10) / 10;
187
+ const imageRatio = Math.round(originalRatio * 10) / 10;
188
+ if (providedRatio !== imageRatio) {
189
+ console.warn(`Image aspect ratio in HTML (${providedRatio}) differs from image aspect ratio (${imageRatio}) - fix width and height or let jampack fill them.`);
190
+ }
191
+ return [w, h];
192
+ }
193
+ else if (width !== undefined && height === undefined) {
194
+ // Width is provided
195
+ width_new = parseInt(width, 10);
196
+ height_new = width_new / originalRatio;
197
+ }
198
+ else if (width === undefined && height !== undefined) {
199
+ // Height is provided
200
+ height_new = parseInt(height, 10);
201
+ width_new = height_new * originalRatio;
202
+ }
203
+ else {
204
+ // No width or height provided - set both to image size
205
+ width_new = meta.width;
206
+ height_new = meta.height;
207
+ }
208
+ // New sizes
209
+ if (width_new !== undefined && height_new !== undefined) {
210
+ img.attr('width', width_new.toFixed(0));
211
+ img.attr('height', height_new.toFixed(0));
212
+ return [Math.round(width_new), Math.round(height_new)];
213
+ }
214
+ // Something when wrong
215
+ throw new Error('Unexpected issue when resolving image sizes');
216
+ }
217
+ export async function optimize(dir) {
218
+ const paths = await globby('**/*.{htm,html}', { cwd: dir });
219
+ // Sequential async
220
+ await paths.reduce(async (previousPromise, item) => {
221
+ await previousPromise;
222
+ return analyse(dir, item);
223
+ }, Promise.resolve());
224
+ return compressedResults;
225
+ }
@@ -0,0 +1,9 @@
1
+ export declare type Options = {
2
+ image: {
3
+ embed_size: number;
4
+ srcset_min_width: number;
5
+ srcset_max_width: number;
6
+ };
7
+ };
8
+ declare const default_options: Options;
9
+ export default default_options;
@@ -0,0 +1,8 @@
1
+ const default_options = {
2
+ image: {
3
+ embed_size: 800,
4
+ srcset_min_width: 640 * 2,
5
+ srcset_max_width: 5120
6
+ }
7
+ };
8
+ export default default_options;
@@ -0,0 +1,5 @@
1
+ export declare type Result = {
2
+ file: string;
3
+ originalSize: number;
4
+ compressedSize: number;
5
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare function formatBytes(bytes: number, decimals?: number): string;
2
+ export declare function isLocal(src: string): boolean;
3
+ export declare function translateSrc(projectRoot: string, htmlRelativePath: string, src: string): string;
4
+ export declare function isNumeric(value: string): boolean;
package/dist/utils.js ADDED
@@ -0,0 +1,25 @@
1
+ import * as url from 'url';
2
+ import * as path from 'path';
3
+ export function formatBytes(bytes, decimals = 2) {
4
+ if (!+bytes)
5
+ return '0 Bytes';
6
+ const k = 1024;
7
+ const dm = decimals < 0 ? 0 : decimals;
8
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
9
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
10
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
11
+ }
12
+ export function isLocal(src) {
13
+ const u = url.parse(src);
14
+ return !u.host;
15
+ }
16
+ export function translateSrc(projectRoot, htmlRelativePath, src) {
17
+ if (!isLocal(src)) {
18
+ throw new Error('Source should be local');
19
+ }
20
+ const srcAbsolutePath = path.join(projectRoot, src.startsWith('/') ? '' : htmlRelativePath, src);
21
+ return srcAbsolutePath;
22
+ }
23
+ export function isNumeric(value) {
24
+ return /^\d+$/.test(value);
25
+ }
package/package.json CHANGED
@@ -1,43 +1,56 @@
1
1
  {
2
2
  "name": "@divriots/jampack",
3
- "version": "0.0.9",
4
- "description": "",
5
- "keywords": [],
6
- "main": "./dist/index.js",
3
+ "version": "0.1.1",
4
+ "description": "Packer for static websites",
5
+ "author": "Georges Gomes @georges-gomes",
7
6
  "bin": {
8
- "jampack": "./dist/jampack.js"
7
+ "jampack": "dist/index.js"
9
8
  },
9
+ "homepage": "https://github.com/divriots/jampack",
10
+ "license": "MIT",
11
+ "main": "dist/index.js",
12
+ "type": "module",
13
+ "repository": "divriots/jampack",
10
14
  "files": [
11
- "dist"
15
+ "/bin",
16
+ "/dist"
12
17
  ],
13
- "engines": {
14
- "node": ">=10.0.0"
18
+ "dependencies": {
19
+ "@swc/core": "^1.3.3",
20
+ "@types/cheerio": "^0.22.31",
21
+ "@types/commander": "^2.12.2",
22
+ "@types/csso": "^5.0.0",
23
+ "@types/html-minifier-terser": "^7.0.0",
24
+ "@types/sharp": "^0.31.0",
25
+ "@types/svgo": "^2.6.4",
26
+ "@types/table": "^6.3.2",
27
+ "cheerio": "0.22.0",
28
+ "commander": "^9.4.0",
29
+ "csso": "^5.0.5",
30
+ "globby": "^13.1.2",
31
+ "html-minifier-terser": "^7.0.0",
32
+ "mini-svg-data-uri": "^1.4.4",
33
+ "sharp": "^0.31.0",
34
+ "svgo": "^2.8.0",
35
+ "table": "^6.8.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^16.9.4",
39
+ "shx": "^0.3.3",
40
+ "ts-node": "^10.2.1",
41
+ "tslib": "^2.3.1",
42
+ "typescript": "^4.4.3"
15
43
  },
16
44
  "scripts": {
17
- "build": "tsc --project ./tsconfig.json",
18
- "watch": "tsc --project ./tsconfig.json --watch",
19
- "test": "echo \"Error: no test specified\" && exit 1"
45
+ "watch": "pnpm build --watch",
46
+ "try": "shx rm -fr demo && cp -R demo_ref demo && node ./dist/index.js",
47
+ "build": "shx rm -rf dist && tsc -b",
48
+ "lint": "eslint . --ext .ts --config .eslintrc"
20
49
  },
21
- "author": "",
22
- "license": "ISC",
23
- "devDependencies": {
24
- "@types/node": "^14.0.5",
25
- "typescript": "^3.9.3"
50
+ "engines": {
51
+ "node": ">=14.0.0"
26
52
  },
27
- "dependencies": {
28
- "@open-wc/rollup-plugin-html": "^1.1.0",
29
- "@rollup/plugin-node-resolve": "^10.0.0",
30
- "@types/commander": "^2.12.2",
31
- "@types/parse5": "^5.0.3",
32
- "commander": "^5.1.0",
33
- "es-dev-server": "^1.54.0",
34
- "fast-glob": "^3.2.2",
35
- "htmlparser2": "^4.1.0",
36
- "posthtml": "^0.13.0",
37
- "posthtml-collect-styles": "0.0.1",
38
- "posthtml-link-noreferrer": "^1.0.7",
39
- "posthtml-minifier": "^0.1.0",
40
- "rollup": "^2.11.2",
41
- "rollup-plugin-terser": "^6.1.0"
42
- }
53
+ "bugs": "https://github.com/divriots/jampack/issues",
54
+ "keywords": [],
55
+ "types": "dist/index.d.ts"
43
56
  }
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const transformNodeResolve_1 = require("./transformNodeResolve");
4
- module.exports = function (eleventyConfig, pluginNamespace) {
5
- console.log("Loading jampack " + require("../../package.json").version);
6
- eleventyConfig.namespace(pluginNamespace, () => {
7
- eleventyConfig.addTransform("html", transformNodeResolve_1.transformNodeResolve);
8
- });
9
- };
@@ -1,73 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.transformNodeResolve = void 0;
26
- const rollup_plugin_html_1 = __importDefault(require("@open-wc/rollup-plugin-html"));
27
- const plugin_node_resolve_1 = __importDefault(require("@rollup/plugin-node-resolve"));
28
- const rollup = __importStar(require("rollup"));
29
- const path_1 = __importDefault(require("path"));
30
- const fs_1 = __importDefault(require("fs"));
31
- const tools_1 = require("../tools");
32
- async function transformNodeResolve(content, outputPath) {
33
- if (!outputPath.endsWith(".html"))
34
- return content;
35
- if (!tools_1.hasScriptModules(content))
36
- return content;
37
- const filename = path_1.default.basename(outputPath);
38
- const outputDir = path_1.default.dirname(outputPath);
39
- const inputOptions = {
40
- plugins: [
41
- rollup_plugin_html_1.default({
42
- rootDir: outputDir,
43
- name: filename,
44
- html: content,
45
- minify: false,
46
- }),
47
- plugin_node_resolve_1.default({
48
- customResolveOptions: {
49
- basedir: process.cwd(),
50
- },
51
- }),
52
- ],
53
- };
54
- const outputOptions = {
55
- format: "esm",
56
- dir: outputDir,
57
- };
58
- const bundle = await rollup.rollup(inputOptions);
59
- // Generate
60
- const { output } = await bundle.generate(outputOptions);
61
- const indexhtml = output.find((asset) => asset.fileName === filename).source;
62
- // Write the rest of the modules
63
- output
64
- .filter((asset) => asset.type === "chunk")
65
- .forEach((asset) => {
66
- if (asset.code !== undefined) {
67
- fs_1.default.mkdirSync(outputDir, { recursive: true });
68
- fs_1.default.writeFileSync(path_1.default.join(outputDir, asset.fileName), asset.code);
69
- }
70
- });
71
- return indexhtml;
72
- }
73
- exports.transformNodeResolve = transformNodeResolve;
package/dist/cmd-bake.js DELETED
@@ -1,24 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const packer_1 = require("./packer");
7
- const fast_glob_1 = __importDefault(require("fast-glob"));
8
- const chalk_1 = __importDefault(require("chalk"));
9
- async function bake(config, options) {
10
- const isProduction = !options.quick;
11
- if (isProduction)
12
- process.env.NODE_ENV = "production";
13
- const entries = await fast_glob_1.default(["**/index.html"], {
14
- cwd: config.rootDir,
15
- followSymbolicLinks: false,
16
- });
17
- if (entries.length <= 0) {
18
- throw new Error("No index.html found in " + config.rootDir);
19
- }
20
- console.log("Targeting:");
21
- entries.forEach((f) => console.log(chalk_1.default.gray("> " + f)));
22
- await packer_1.pack(config.rootDir, entries, isProduction, config);
23
- }
24
- exports.default = bake;
package/dist/cmd-serve.js DELETED
@@ -1,41 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const es_dev_server_1 = require("es-dev-server");
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
- function createSymlinkToNodeModules(config) {
10
- const rootNodeModules = path_1.default.join(config.rootDir, "node_modules");
11
- try {
12
- fs_1.default.unlinkSync(rootNodeModules);
13
- }
14
- catch (e) {
15
- // No problem
16
- }
17
- try {
18
- fs_1.default.symlinkSync(path_1.default.join(process.cwd(), config.modulesDir), rootNodeModules);
19
- }
20
- catch (e) {
21
- // Let's assume it's all good
22
- console.error("Error creating symlink to node_modules", e);
23
- }
24
- return rootNodeModules;
25
- }
26
- function serve(config) {
27
- // Set development mode
28
- process.env.NODE_ENV = "development";
29
- const rootNodeModules = createSymlinkToNodeModules(config);
30
- es_dev_server_1.startServer(es_dev_server_1.createConfig({
31
- port: config.servePort,
32
- rootDir: config.rootDir,
33
- logStartup: true,
34
- logErrorsToBrowser: true,
35
- watch: true,
36
- nodeResolve: true,
37
- moduleDirs: [rootNodeModules],
38
- preserveSymlinks: true,
39
- }));
40
- }
41
- exports.default = serve;
@@ -1,62 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.loadConfig = void 0;
26
- const path_1 = __importDefault(require("path"));
27
- const fs_1 = __importDefault(require("fs"));
28
- const defaultConfig = {
29
- // Source folder
30
- rootDir: "./dist",
31
- // Where to start resolving node_modules from
32
- modulesDir: "./",
33
- // jampack serve listening port
34
- servePort: 8080,
35
- // Processors
36
- proc_html_minifier: {
37
- collapseWhitespace: true,
38
- minifyCSS: true,
39
- removeComments: true,
40
- removeRedundantAttributes: true,
41
- minifyJS: true,
42
- sortAttributes: true,
43
- sortClassName: true,
44
- },
45
- proc_html_link_noreferrer: true,
46
- };
47
- const configFileName = path_1.default.join(process.cwd(), "./.jampack.config.js");
48
- async function loadConfig() {
49
- let config = defaultConfig;
50
- if (fs_1.default.existsSync(configFileName)) {
51
- const localConfig = await Promise.resolve().then(() => __importStar(require(configFileName)));
52
- config = Object.assign(defaultConfig, localConfig);
53
- }
54
- else {
55
- console.log("No config file - Using default config\n");
56
- }
57
- console.log("Using config:");
58
- console.log(config);
59
- console.log("");
60
- return config;
61
- }
62
- exports.loadConfig = loadConfig;
package/dist/jampack.js DELETED
@@ -1,32 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const commander_1 = __importDefault(require("commander"));
8
- const cmd_serve_js_1 = __importDefault(require("./cmd-serve.js"));
9
- const cmd_bake_js_1 = __importDefault(require("./cmd-bake.js"));
10
- const package_json_1 = __importDefault(require("../package.json"));
11
- const default_config_js_1 = require("./default.config.js");
12
- commander_1.default.version(package_json_1.default.version, "-v, --version");
13
- commander_1.default
14
- .command("bake")
15
- .description("Optimize site for production")
16
- .option("--quick", "Fast optimisation (for development)")
17
- .action(async function (options) {
18
- cmd_bake_js_1.default(await default_config_js_1.loadConfig(), options);
19
- });
20
- commander_1.default
21
- .command("serve")
22
- .description("Serves website for development - resolves bare import on demand")
23
- .action(async function (options) {
24
- cmd_serve_js_1.default(await default_config_js_1.loadConfig());
25
- });
26
- commander_1.default.parse(process.argv);
27
- ["exit", "SIGINT"].forEach((event) => {
28
- // @ts-ignore
29
- process.on(event, () => {
30
- process.exit(0);
31
- });
32
- });
package/dist/packer.js DELETED
@@ -1,132 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.pack = void 0;
26
- const rollup = __importStar(require("rollup"));
27
- const rollup_plugin_terser_1 = require("rollup-plugin-terser");
28
- const rollup_plugin_html_1 = __importDefault(require("@open-wc/rollup-plugin-html"));
29
- const plugin_node_resolve_1 = __importDefault(require("@rollup/plugin-node-resolve"));
30
- const path_1 = __importDefault(require("path"));
31
- const chalk_1 = __importDefault(require("chalk"));
32
- const tools_1 = require("./tools");
33
- const posthtml_1 = __importDefault(require("posthtml"));
34
- const posthtml_minifier_1 = __importDefault(require("posthtml-minifier"));
35
- const posthtml_link_noreferrer_1 = __importDefault(require("posthtml-link-noreferrer"));
36
- const posthtml_collect_styles_1 = __importDefault(require("posthtml-collect-styles"));
37
- const fs_1 = __importDefault(require("fs"));
38
- async function processJSModules(rootDir, files, isProduction, config) {
39
- const htmlfiles = Object.entries(files)
40
- .filter(([, content]) => tools_1.hasScriptModules(content))
41
- .map(([name, content]) => {
42
- return {
43
- name: name,
44
- html: content,
45
- };
46
- });
47
- const htmlplugin = rollup_plugin_html_1.default({
48
- rootDir,
49
- html: htmlfiles,
50
- minify: false,
51
- });
52
- if (htmlfiles.length < 1) {
53
- // Nothing to do here
54
- return;
55
- }
56
- const plugins = [htmlplugin];
57
- plugins.push(plugin_node_resolve_1.default({
58
- customResolveOptions: {
59
- basedir: config.modulesDir,
60
- },
61
- }));
62
- if (isProduction)
63
- plugins.push(rollup_plugin_terser_1.terser());
64
- const inputOptions = {
65
- plugins,
66
- };
67
- const outputOptions = {
68
- format: "esm",
69
- dir: rootDir,
70
- };
71
- const bundle = await rollup.rollup(inputOptions);
72
- const { output } = await bundle.generate(outputOptions);
73
- // Write the rest of the modules
74
- output.forEach((asset) => {
75
- files[asset.fileName] = asset.code || asset.source;
76
- });
77
- }
78
- async function processHTML(rootDir, files, isProduction, config) {
79
- const htmlTransform = async (html) => {
80
- let ph = posthtml_1.default();
81
- if (config.proc_html_link_noreferrer === true) {
82
- ph = ph.use(posthtml_link_noreferrer_1.default());
83
- }
84
- if (true) {
85
- ph = ph.use(posthtml_collect_styles_1.default());
86
- }
87
- if (config.proc_html_minifier !== false) {
88
- ph = ph.use(posthtml_minifier_1.default(config.proc_html_minifier));
89
- }
90
- return ph.process(html).then(function (result) {
91
- return result.html;
92
- });
93
- };
94
- const tasks = Object.entries(files).map(async ([name, data]) => {
95
- if (name.endsWith(".html"))
96
- files[name] = await htmlTransform(data);
97
- });
98
- return Promise.all(tasks);
99
- }
100
- async function pack(rootDir, entries, isProduction, config) {
101
- if (entries.length < 1) {
102
- throw new Error("No entry files to process");
103
- }
104
- // Load entries
105
- const originalFiles = {};
106
- entries.forEach((e) => {
107
- const name = e;
108
- const content = fs_1.default.readFileSync(path_1.default.join(rootDir, name)).toString();
109
- originalFiles[name] = content;
110
- });
111
- const processedFiles = Object.assign({}, originalFiles);
112
- console.log(chalk_1.default.green("Packing..."));
113
- const start_pack = Date.now();
114
- await processJSModules(rootDir, processedFiles, isProduction, config);
115
- if (isProduction) {
116
- await processHTML(rootDir, processedFiles, isProduction, config);
117
- }
118
- // Packing done
119
- const end_pack = Date.now();
120
- console.log(chalk_1.default.green("Packed in " + (end_pack - start_pack) + "ms"));
121
- // Writing files
122
- Object.entries(processedFiles).forEach(([name, content]) => {
123
- if (!originalFiles[name] || originalFiles[name] !== content) {
124
- console.log("Writing: " + name);
125
- fs_1.default.writeFile(path_1.default.join(rootDir, name), content, (err) => {
126
- if (err)
127
- throw err;
128
- });
129
- }
130
- });
131
- }
132
- exports.pack = pack;
package/dist/tools.js DELETED
@@ -1,19 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasScriptModules = void 0;
4
- const htmlparser2_1 = require("htmlparser2");
5
- function hasScriptModules(content) {
6
- let hasmodule = false;
7
- const parser = new htmlparser2_1.Parser({
8
- onopentag(name, attribs) {
9
- if (name === "script" && attribs.type === "module") {
10
- hasmodule = true;
11
- parser.done();
12
- }
13
- },
14
- });
15
- parser.write(content);
16
- parser.end();
17
- return hasmodule;
18
- }
19
- exports.hasScriptModules = hasScriptModules;