@reflex-stack/tsp 0.1.0

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,132 @@
1
+ import { execAsync } from "@zouloux/cli"
2
+ import { Directory, File } from "@zouloux/files"
3
+ import { statSync, writeFileSync } from 'node:fs'
4
+ import brotliSize from "brotli-size"
5
+ import { join, relative } from "node:path";
6
+ import { naiveHumanFileSize } from "../utils.js";
7
+
8
+
9
+ const keepClassNames = false;
10
+ const keepFunctionNames = false;
11
+
12
+ const defaultTerserOptions = [
13
+ // Compress and shorten names
14
+ '--compress', [
15
+ `ecma=2017`,
16
+ 'passes=3',
17
+ `keep_classnames=${keepClassNames}`,
18
+ `keep_fnames=${keepFunctionNames}`,
19
+ 'dead_code=true',
20
+ 'unsafe_arrows=true',
21
+ 'unsafe_methods=true',
22
+ 'unsafe_undefined=true',
23
+ 'keep_fargs=false',
24
+ 'conditionals=false'
25
+ ].join(","),
26
+ // Mangle variables and functions
27
+ '--mangle', [
28
+ 'toplevel=true',
29
+ `keep_classnames=${keepClassNames}`,
30
+ `keep_fnames=${keepFunctionNames}`
31
+ ].join(","),
32
+ // Mangle properties starting with an underscore
33
+ `--mangle-props`, [`regex=/^_/`].join(','),
34
+ // Set env as production for dead code elimination
35
+ `-d 'process.env.NODE_ENV="production"'`,
36
+ // Threat as module (remove "use strict")
37
+ '--module',
38
+ '--toplevel',
39
+ ];
40
+
41
+
42
+ /**
43
+ * Compress and check brotli sizes
44
+ * @param bundles array [".", "./submodule"]
45
+ * @param config tsp config
46
+ * @returns {Promise<*[]>}
47
+ */
48
+ export async function sizeReport ( bundles, config ) {
49
+ // Browse all bundles
50
+ // A bundle is defined by the "export" property of package.json
51
+ // The module resolution does not exist and all files in the bundle'e directory
52
+ // Is considered part of the bundle.
53
+ const allBundleSizes = []
54
+ for ( const bundlePath of bundles ) {
55
+ // Get all built JS files of this bundle directory
56
+ // Bundle directories are relative to dist
57
+ const bundleDirectory = new Directory( join( config.cwd, config.dist, bundlePath ) )
58
+ let allFiles = await bundleDirectory.children('file')
59
+ allFiles = allFiles
60
+ .filter( f => f.isFile() && f.extensions?.[0]?.toLowerCase() === "js" )
61
+ .map( f => f.path )
62
+ const bundleName = bundlePath.replace(/^\.\//, '')
63
+ const bundleSizes = {
64
+ name: bundleName === "." ? "main" : bundleName,
65
+ files: [],
66
+ sizes: [0, 0, 0],
67
+ }
68
+ for ( const filePath of allFiles ) {
69
+ const relativeFilePath = relative( join(config.cwd, config.dist), filePath )
70
+ const fileReport = {
71
+ path: relativeFilePath,
72
+ sizes: []
73
+ }
74
+ const output = `tmp/${relativeFilePath}`
75
+ // Weight original js file ( not compressed )
76
+ const size0 = statSync(filePath).size
77
+ // Minify using terser
78
+ const command = [ "terser", ...defaultTerserOptions, `-o ${output}`, `-- ${filePath}` ].join(" ")
79
+ await execAsync(command, 3)
80
+ // Weight minified file size
81
+ const size1 = statSync(output).size
82
+ // Weight compressed file size
83
+ const size2 = brotliSize.fileSync( output )
84
+ fileReport.sizes = [ size0, size1, size2 ]
85
+ // Count for total bundle size
86
+ bundleSizes.sizes[0] += size0
87
+ bundleSizes.sizes[1] += size1
88
+ bundleSizes.sizes[2] += size2
89
+ // Register this file report
90
+ bundleSizes.files.push( fileReport )
91
+ }
92
+ allBundleSizes.push(bundleSizes)
93
+ }
94
+ // Delete temp directory
95
+ const tmp = new Directory( join( config.cwd, config.tmp ) )
96
+ await tmp.delete()
97
+
98
+ return allBundleSizes
99
+ }
100
+
101
+ export async function cleanSizeReports ( config ) {
102
+ const dir = new Directory( join(config.cwd, config.reports) )
103
+ await dir.ensureParents()
104
+ await dir.clean()
105
+ }
106
+
107
+ export function generateJSON ( sizeReport, config ) {
108
+ writeFileSync(
109
+ join(config.cwd, config.reports, 'size-report.json'),
110
+ JSON.stringify( sizeReport ),
111
+ { encoding: 'utf-8' }
112
+ )
113
+ }
114
+
115
+ export async function generateSVGs ( sizeReport, config ) {
116
+ for ( const bundle of sizeReport ) {
117
+ const size = bundle.sizes[2]
118
+ const sizeContent = naiveHumanFileSize( size )
119
+ for ( const scheme of ["light", "dark"] ) {
120
+ const bitPath = join( config.cwd, config.reports, `${bundle.name}-${scheme}.svg` )
121
+ const svgBitFile = new File( bitPath );
122
+ svgBitFile.content( () => [
123
+ `<svg width="${sizeContent.length * 10}" height="22" xmlns="http://www.w3.org/2000/svg">`,
124
+ `<style> text { fill: ${scheme === "dark" ? "white" : "black" }; font-family: 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; }</style>`,
125
+ `<text y="21">${sizeContent}</text>`,
126
+ `</svg>`,
127
+ ].join(""))
128
+ await svgBitFile.ensureParents()
129
+ await svgBitFile.save();
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,20 @@
1
+ import { execAsync, newLine, nicePrint, oraTask } from "@zouloux/cli";
2
+
3
+
4
+ export async function test ( config ) {
5
+ nicePrint(`Starting test sequence`)
6
+ newLine()
7
+ for ( const testFile of config["test-files"] ) {
8
+ const command = `${config.runtime} ${config.tests}/${testFile}`
9
+ nicePrint(`{d}$ ${command}`)
10
+ try {
11
+ await execAsync(command, 3)
12
+ }
13
+ catch ( error ) {
14
+ console.error(error)
15
+ nicePrint(`{b/g}Test ${testFile} failed`, { code: error.code })
16
+ }
17
+ newLine()
18
+ }
19
+ await oraTask(`All tests passed`, (t) => {t.success()})
20
+ }
package/src/config.js ADDED
@@ -0,0 +1,18 @@
1
+
2
+ export let getConfig = (userPackage) => {
3
+ return {
4
+ cwd: process.cwd(),
5
+ // Default config
6
+ runtime: "node",
7
+ src: './src',
8
+ dist: './dist',
9
+ tests: './tests',
10
+ "test-files": ['test.js'],
11
+ tmp: './tmp',
12
+ reports: './reports',
13
+ 'generate-json-report': true,
14
+ 'generate-svg-report': true,
15
+ // Override with package config
16
+ ...((userPackage ?? {})['tsp'] ?? {})
17
+ }
18
+ }
File without changes
File without changes
package/src/utils.js ADDED
@@ -0,0 +1,61 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { newLine, nicePrint } from "@zouloux/cli";
5
+ import { getConfig } from "./config.js";
6
+ import { execSync } from 'child_process'
7
+
8
+ export const getScriptDirectoryName = (importMetaUrl) => dirname(fileURLToPath(importMetaUrl))
9
+
10
+ export function getUserPackageJson ( noThrow = false ) {
11
+ // Get default config to have the cwd
12
+ const config = getConfig()
13
+ const filePath = join(`${config.cwd}/package.json`)
14
+ if (!existsSync(filePath)) {
15
+ if ( noThrow )
16
+ return null
17
+ nicePrint(`{b/r}File ${filePath} not found in ${config.cwd}.`, { code: 1 })
18
+ }
19
+ return readJsonFile( filePath )
20
+ }
21
+
22
+ export function getTSPPackageJson () {
23
+ const tspDirectory = getScriptDirectoryName(import.meta.url)
24
+ return readJsonFile( join(`${tspDirectory}/../package.json`), )
25
+ }
26
+
27
+ export function readJsonFile ( filePath ) {
28
+ const jsonAsText = readFileSync( filePath, { encoding: 'utf-8' } )
29
+ return JSON.parse( jsonAsText.toString() )
30
+ }
31
+
32
+ export function showIntroMessage ( noThrow = false ) {
33
+ // User package json
34
+ const userPackageJson = getUserPackageJson( noThrow )
35
+ if ( userPackageJson )
36
+ nicePrint(`{w}Working on {w/b}${userPackageJson.name}{d} {w}v${userPackageJson.version}`)
37
+ // tsp package json
38
+ const tspPackageJson = getTSPPackageJson()
39
+ nicePrint(`{w}Using {w/b}${tspPackageJson.name}{d} {w}v${tspPackageJson.version}`)
40
+ newLine()
41
+ }
42
+
43
+ /**
44
+ * Show bytes report as bytes or kilobytes.
45
+ * Very naive implementation.
46
+ */
47
+ export function naiveHumanFileSize ( size ) {
48
+ if ( size > 1000 ) // is it base 10 or ^2 ?
49
+ size = ~~(size / 10) / 100 + 'k'
50
+ return size + 'b'
51
+ }
52
+
53
+ // Get git remote repo URL if in a git repo
54
+ export function getGitRemoteUrl ( cwd ) {
55
+ try {
56
+ return execSync('git remote get-url origin', { stdio: 'pipe', cwd }).toString().trim();
57
+ }
58
+ catch {
59
+ return '';
60
+ }
61
+ }
@@ -0,0 +1,13 @@
1
+ # ecma-build test package
2
+
3
+ Main bundle is only
4
+ <picture style="display: inline-block">
5
+ <source media="(prefers-color-scheme: dark)" srcset="./reports/main-dark.svg">
6
+ <img src="./reports/main-light.svg">
7
+ </picture>
8
+
9
+ Optional sub-module is only
10
+ <picture style="display: inline-block">
11
+ <source media="(prefers-color-scheme: dark)" srcset="./reports/submodule-dark.svg">
12
+ <img src="./reports/submodule-light.svg">
13
+ </picture>
@@ -0,0 +1 @@
1
+ export declare const commonDep = "common dependency";
@@ -0,0 +1 @@
1
+ export const commonDep = "common dependency";
@@ -0,0 +1,2 @@
1
+ export * from "./root-dep.js";
2
+ export { doStuff } from "./stuff.js";
@@ -0,0 +1,2 @@
1
+ export * from "./root-dep.js";
2
+ export { doStuff } from "./stuff.js";
@@ -0,0 +1 @@
1
+ export declare const rootDep = "root dependency";
@@ -0,0 +1 @@
1
+ export const rootDep = "root dependency";
@@ -0,0 +1 @@
1
+ export declare function doStuff(): void;
@@ -0,0 +1,4 @@
1
+ import { commonDep } from "./common-dep.js";
2
+ export function doStuff() {
3
+ console.log("Do stuff", commonDep);
4
+ }
@@ -0,0 +1 @@
1
+ export declare function doSubmoduleStuff(): void;
@@ -0,0 +1,5 @@
1
+ import { submoduleDep } from "./submodule-dep.js";
2
+ import { commonDep } from "../common-dep.js";
3
+ export function doSubmoduleStuff() {
4
+ console.log("Do submodule stuff", submoduleDep, commonDep);
5
+ }
@@ -0,0 +1 @@
1
+ export declare const submoduleDep = "submodule dependency";
@@ -0,0 +1 @@
1
+ export const submoduleDep = "submodule dependency";