@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.
- package/README.md +65 -0
- package/package.json +23 -0
- package/src/cli.js +225 -0
- package/src/commands/build.js +15 -0
- package/src/commands/init.js +245 -0
- package/src/commands/size-report.js +132 -0
- package/src/commands/test.js +20 -0
- package/src/config.js +18 -0
- package/src/scaffold/README.md.template +0 -0
- package/src/scaffold/tsconfig.json.template +0 -0
- package/src/utils.js +61 -0
- package/tests/example-package/README.md +13 -0
- package/tests/example-package/dist/common-dep.d.ts +1 -0
- package/tests/example-package/dist/common-dep.js +1 -0
- package/tests/example-package/dist/index.d.ts +2 -0
- package/tests/example-package/dist/index.js +2 -0
- package/tests/example-package/dist/root-dep.d.ts +1 -0
- package/tests/example-package/dist/root-dep.js +1 -0
- package/tests/example-package/dist/stuff.d.ts +1 -0
- package/tests/example-package/dist/stuff.js +4 -0
- package/tests/example-package/dist/submodule/index.d.ts +1 -0
- package/tests/example-package/dist/submodule/index.js +5 -0
- package/tests/example-package/dist/submodule/submodule-dep.d.ts +1 -0
- package/tests/example-package/dist/submodule/submodule-dep.js +1 -0
- package/tests/example-package/package-lock.json +880 -0
- package/tests/example-package/package.json +35 -0
- package/tests/example-package/tsconfig.json +18 -0
|
@@ -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 @@
|
|
|
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 @@
|
|
|
1
|
+
export declare function doSubmoduleStuff(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const submoduleDep = "submodule dependency";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const submoduleDep = "submodule dependency";
|